package com.pig4cloud.pig.ads.service.impl;

import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dy.yunying.api.entity.ParentGameDO;
import com.dy.yunying.api.entity.WanGameDO;
import com.dy.yunying.api.feign.RemoteGameService;
import com.dy.yunying.api.feign.RemotePGameService;
import com.dy.yunying.api.feign.RemoteRoleGameService;
import com.dy.yunying.api.req.ParentGameReq;
import com.dy.yunying.api.req.WanGameReq;
import com.idrsolutions.image.png.PngCompressor;
import com.pig4cloud.pig.ads.gdt.service.GdtAccesstokenService;
import com.pig4cloud.pig.ads.pig.mapper.AdOperateFileMapper;
import com.pig4cloud.pig.ads.pig.mapper.AdOperateGameMapper;
import com.pig4cloud.pig.ads.pig.mapper.AdOperateMaterialMapper;
import com.pig4cloud.pig.ads.pig.mapper.gdt.GdtBrandImageMapper;
import com.pig4cloud.pig.ads.service.AdOperateMaterialService;
import com.pig4cloud.pig.ads.service.TtAccesstokenService;
import com.pig4cloud.pig.ads.utils.OEHttpUtils;
import com.pig4cloud.pig.api.entity.AdOperateFileDO;
import com.pig4cloud.pig.api.entity.AdOperateGameDO;
import com.pig4cloud.pig.api.entity.AdOperateMaterialDO;
import com.pig4cloud.pig.api.entity.GdtBrandImage;
import com.pig4cloud.pig.api.vo.AdOperateMaterialVO;
import com.pig4cloud.pig.api.vo.AdOperateMaterialVO.MaterialFile;
import com.pig4cloud.pig.api.vo.PushIconVO;
import com.pig4cloud.pig.common.core.constant.enums.PlatformTypeEnum;
import com.pig4cloud.pig.common.core.util.ECollectionUtil;
import com.pig4cloud.pig.common.core.util.R;
import com.pig4cloud.pig.common.security.util.SecurityUtils;
import com.sun.imageio.plugins.png.PNGImageReader;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import sun.awt.image.ImageFormatException;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.ZoneOffset;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 *
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AdOperateMaterialServiceImpl extends ServiceImpl<AdOperateMaterialMapper, AdOperateMaterialDO> implements AdOperateMaterialService {

	private static final ZoneOffset DONGBA_DISTRICT = ZoneOffset.ofHours(8);

	private static final String[] ALLOW_IMAGE_FORMAT_NAMES = {"JPG", "jpg", "JPEG", "jpeg", "PNG", "png"};

	private static final long TT_ICON_MAX_SIZE = 1024 * 1024, GDT_ICON_MAX_SIZE = 50 * 1024;

	private static final int TT_ICON_PIXEL_SIZE = 108, GDT_ICON_PIXEL_SIZE = 512;

	/*@Value("${operate.material.gdtIconMaxSize}")
	private Long gdtIconMaxSize;

	@Value("${operate.material.gdtIconPixelSize}")
	private Integer gdtIconPixelSize;*/

	@Value("${operate.material.url:http://localhost:8080}")
	private String rootUrl;

	@Value("${operate.material.absolute-path:/upload}")
	private String absolutePath;

	@Value("${operate.material.relative-path:/upload}")
	private String relativePath;

	private final AdOperateMaterialMapper adOperateMaterialMapper;

	private final AdOperateGameMapper adOperateGameMapper;

	private final AdOperateFileMapper adOperateFileMapper;

	private final GdtBrandImageMapper gdtBrandImageMapper;

	private final TtAccesstokenService ttAccesstokenService;

	private final GdtAccesstokenService gdtAccesstokenService;

	private final RemoteRoleGameService remoteRoleGameService;

	private final RemotePGameService remotePGameService;

	private final RemoteGameService remoteGameService;

	/**
	 * 获取运营素材列表
	 *
	 * @param aom
	 * @return
	 */
	@Override
	public List<AdOperateMaterialDO> materialList(AdOperateMaterialVO aom) {
		Set<Long> rGameIds = ECollectionUtil.stringToLongSet(aom.getGameidArr());
		// 构建查询条件对象
		AdOperateMaterialDO record = new AdOperateMaterialDO().setCreateUserId(aom.getCreateUserId()).setMakeUserId(aom.getMakeUserId()).setMaterialType(aom.getMaterialType()).setPgids(ECollectionUtil.stringToLongSet(aom.getPgidArr()));

		// 获取当前账户下子游戏信息
		List<Long> gameIds = Optional.ofNullable(remoteRoleGameService.getOwnerRoleGameIds()).map(R::getData).orElse(Collections.emptyList());
		if (gameIds.isEmpty()) { // 当前用户所属角色下没有子游戏
			return Collections.emptyList();
		}
		if (!rGameIds.isEmpty() && (gameIds = rGameIds.stream().filter(gameIds::contains).collect(Collectors.toList())).isEmpty()) {
			return Collections.emptyList();
		}
		record.setGameids(gameIds);

		List<AdOperateMaterialDO> list = adOperateMaterialMapper.list(record);
		// 查询运营素材列表
		if (!list.isEmpty()) {
			Set<Long> materialIds = list.stream().map(AdOperateMaterialDO::getMaterialId).collect(Collectors.toSet());
			// 组装游戏名称
			assemblyGameName(materialIds, list);
			// 组装素材文件
			assemblyMaterialFile(materialIds, list);
		}
		return list;
	}

	/**
	 * 获取运营素材分页列表
	 *
	 * @param page
	 * @param aom
	 * @return
	 */
	@Transactional(readOnly = true, rollbackFor = Exception.class)
	@Override
	public IPage<AdOperateMaterialDO> materialPage(Page<Object> page, AdOperateMaterialVO aom) {
		Set<Long> rGameIds = ECollectionUtil.stringToLongSet(aom.getGameidArr());
		// 构建查询条件对象
		AdOperateMaterialDO record = new AdOperateMaterialDO().setCreateUserId(aom.getCreateUserId()).setMakeUserId(aom.getMakeUserId()).setMaterialType(aom.getMaterialType()).setPgids(ECollectionUtil.stringToLongSet(aom.getPgidArr()));

		// 获取当前账户下子游戏信息
		List<Long> gameIds = Optional.ofNullable(remoteRoleGameService.getOwnerRoleGameIds()).map(R::getData).orElse(Collections.emptyList());
		if (gameIds.isEmpty()) { // 当前用户所属角色下没有子游戏
			return new Page<AdOperateMaterialDO>().setCurrent(page.getCurrent()).setSize(page.getSize()).setTotal(0).setRecords(Collections.emptyList());
		}
		if (!rGameIds.isEmpty() && (gameIds = rGameIds.stream().filter(gameIds::contains).collect(Collectors.toList())).isEmpty()) {
			return new Page<AdOperateMaterialDO>().setCurrent(page.getCurrent()).setSize(page.getSize()).setTotal(0).setRecords(Collections.emptyList());
		}
		record.setGameids(gameIds);

		// 查询运营素材分页列表
		IPage<AdOperateMaterialDO> pageList = adOperateMaterialMapper.pageList(page, record);
		List<AdOperateMaterialDO> records = pageList.getRecords();
		if (records.isEmpty()) {
			return pageList;
		}
		Set<Long> materialIds = records.stream().map(AdOperateMaterialDO::getMaterialId).collect(Collectors.toSet());
		// 组装游戏名称
		assemblyGameName(materialIds, records);
		// 组装素材文件
		assemblyMaterialFile(materialIds, records);
		return pageList;
	}

	private void assemblyGameName(Collection<Long> materialIds, List<AdOperateMaterialDO> records) {
		// 查询父游戏名称
		R<List<ParentGameDO>> pgameList = remotePGameService.getCacheablePGameList(new ParentGameReq().setIds(records.stream().map(AdOperateMaterialDO::getPgid).collect(Collectors.toSet())));
		Map<Long, String> pgameMap = pgameList.getData().stream().collect(Collectors.toMap(ParentGameDO::getId, ParentGameDO::getGname, (k1, k2) -> k1));
		// 查询子游戏ID
		List<AdOperateGameDO> oglist = adOperateGameMapper.selectList(Wrappers.<AdOperateGameDO>lambdaQuery().select(AdOperateGameDO::getMaterialId, AdOperateGameDO::getGameid).in(AdOperateGameDO::getMaterialId, materialIds));
		Map<Long, Set<Long>> ogmap = new HashMap<>();
		for (AdOperateGameDO ogame : oglist) {
			ogmap.computeIfAbsent(ogame.getMaterialId(), (key) -> new HashSet<>()).add(ogame.getGameid());
		}

		// 查询子游戏名称
		R<List<WanGameDO>> gameList = remoteGameService.getCacheableGameList(new WanGameReq().setIds(oglist.stream().map(AdOperateGameDO::getGameid).collect(Collectors.toSet())));
		Map<Long, String> gameMap = gameList.getData().stream().collect(Collectors.toMap(WanGameDO::getId, WanGameDO::getGname, (k1, k2) -> k1));
		// 遍历素材填充主游戏名称和子游戏名称
		for (AdOperateMaterialDO material : records) {
			material.setPgname(pgameMap.get(material.getPgid()));
			StringJoiner joiner = new StringJoiner(",");
			for (Long gameId : ogmap.get(material.getMaterialId())) {
				String gname = gameMap.get(gameId);
				if (StringUtils.isNotEmpty(gname)) {
					joiner.add(gname);
				}
			}
			material.setGname(joiner.toString());
		}
	}

	private void assemblyMaterialFile(Collection<Long> materialIds, List<AdOperateMaterialDO> records) {
		// 查询当前页运营素材的所有文件
		List<AdOperateFileDO> operateFileList = adOperateFileMapper.selectList(Wrappers.<AdOperateFileDO>lambdaQuery().in(AdOperateFileDO::getMaterialId, materialIds));
		for (AdOperateFileDO file : operateFileList) {
			// 将运营素材文件的本地文件系统地址替换为网络资源地址
			file.setFilePath(String.format("%s%s/%s", rootUrl, this.relativePath.replace("\\", "/"), Paths.get(file.getFilePath()).getFileName().toString()));
			// 构建文件与运营素材之间的关系
			for (AdOperateMaterialDO material : records) {
				if (null != material.getMaterialId() && material.getMaterialId().equals(file.getMaterialId())) {
					List<AdOperateFileDO> fileList = material.getFileList();
					if (null == fileList) {
						fileList = new LinkedList<>();
						material.setFileList(fileList);
					}
					fileList.add(file);
				}
			}
		}
	}

	/**
	 * 根据主键获取运营素材信息
	 *
	 * @param materialId
	 * @return
	 */
	@Override
	public AdOperateMaterialDO getMaterial(Long materialId) {
		AdOperateMaterialDO material = adOperateMaterialMapper.selectById(materialId);
		if (null == material) {
			return null;
		}
		// 查询子游戏
		material.setGameList(adOperateGameMapper.selectList(Wrappers.<AdOperateGameDO>lambdaQuery().select(AdOperateGameDO::getId, AdOperateGameDO::getGameid).eq(AdOperateGameDO::getMaterialId, materialId)));
		// 查询运营素材文件
		List<AdOperateFileDO> fileList = adOperateFileMapper.selectList(Wrappers.<AdOperateFileDO>lambdaQuery().eq(AdOperateFileDO::getMaterialId, materialId));
		material.setFileList(fileList);
		for (AdOperateFileDO file : fileList) {
			// 将运营素材文件的本地文件系统地址替换为网络资源地址
			file.setFilePath(String.format("%s%s/%s", rootUrl, this.relativePath.replace("\\", "/"), Paths.get(file.getFilePath()).getFileName().toString()));
		}
		return material;
	}

	/**
	 * 上传运营素材
	 *
	 * @param aom
	 * @throws IOException
	 */
	@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class)
	@Override
	public void uploadMaterial(AdOperateMaterialVO aom) throws Exception {
		Collection<MaterialFile> materialFiles = assertIconMultipartFiles(aom);

		Integer currentUserId = Objects.requireNonNull(SecurityUtils.getUser()).getId();
		Date now = new Date();
		// 插入运营素材记录
		AdOperateMaterialDO material = new AdOperateMaterialDO().setPgid(aom.getPgid()).setGameid(aom.getGameid()).setCreateUserId(aom.getCreateUserId()).setMakeUserId(aom.getMakeUserId()).setMaterialType(aom.getMaterialType()).setUpdateuser(currentUserId).setUpdatetime(now);
		// 保存运营素材信息
		adOperateMaterialMapper.insert(material);
		// 保存运营素材子游戏信息
		Long materialId = material.getMaterialId();
		Set<Long> gameIds = Arrays.stream(aom.getGameidArr().split(",")).map(Long::valueOf).collect(Collectors.toSet());
		for (Long gameId : gameIds) {
			adOperateGameMapper.insert(new AdOperateGameDO().setMaterialId(materialId).setGameid(gameId));
		}
		// 创建文件存放的目录
		Path dirPath = Paths.get(absolutePath, relativePath);
		if (!createDirectory(dirPath)) {
			throw new IOException(dirPath.toString() + " 目录创建失败");
		}
		for (MaterialFile materialFile : materialFiles) {
			AdOperateFileDO aof = processFileName(materialFile).setMaterialId(materialId).setUpdateuser(currentUserId).setUpdatetime(now);
			// 处理文件字节内容，并返回文件实际要存放的地址
			MultipartFile mFile = materialFile.getFile();
			Path filePath = processFileBytes(dirPath, mFile, aof);
			// 插入文件记录
			adOperateFileMapper.insert(aof);
			// 写入文件
			if (!Files.exists(filePath)) {
				Files.write(filePath, mFile.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
			}
		}
	}

	private Collection<MaterialFile> assertIconMultipartFiles(AdOperateMaterialVO aom) throws IOException, ImageFormatException {
		Collection<MaterialFile> materialFiles = aom.getMaterialFiles();
		if (0 == aom.getMaterialType()) {
			// 校验Icon
			if (materialFiles.size() != 1) {
				throw new IllegalArgumentException("Icon类型只能上传一个图片");
			}
			if (adOperateFileMapper.selectCountByFileName(0, assertFileName(materialFiles.iterator().next())) > 0) {
				throw new IllegalArgumentException("Icon类型图片名称已存在，请重命名");
			}
		} else if (1 == aom.getMaterialType()) {
			// 校验五图
			if (materialFiles.size() == 0 || materialFiles.size() > 5) {
				throw new IllegalArgumentException("五图类型图片数量不能小于1并且不能大于5");
			}
			if (materialFiles.stream().map(MaterialFile::getName).distinct().count() != materialFiles.size()) {
				throw new IllegalArgumentException("五图多张图片名称不能重复");
			}
		} else {
			// 校验其他类型
			if (materialFiles.size() != 1) {
				throw new IllegalArgumentException("只能上传一个图片");
			}
		}
		// 校验图片类型
		for (MaterialFile materialFile : materialFiles) {
			assertFileName(materialFiles.iterator().next());
			ImageReader reader = assertImageReader(materialFile.getFile().getInputStream(), ALLOW_IMAGE_FORMAT_NAMES);
			// 校验图片尺寸
			if (0 == aom.getMaterialType() && reader.getWidth(0) != reader.getHeight(0)) {
				throw new ImageFormatException("Icon类型图片尺寸比例必须为1:1");
			}
		}
		return materialFiles;
	}

	private static String assertFileName(MaterialFile materialFile) {
		String fileName = materialFile.getName();
		int fileNameLen = StringUtils.length(fileName);
		if (fileNameLen == 0 || fileNameLen > 8) {
			throw new IllegalArgumentException("图片名称长度不能小于1并且不能大于8");
		}
		return fileName;
	}

	private static ImageReader assertImageReader(InputStream input, String... allowImageFormatNames) throws ImageFormatException, IOException {
		// 校验图片类型，必须为JPG/JPEG或PNG
		ImageInputStream iis = ImageIO.createImageInputStream(input);
		Iterator<ImageReader> irs = ImageIO.getImageReaders(iis);
		ImageReader reader;
		if (!irs.hasNext() || !ArrayUtils.contains(allowImageFormatNames, (reader = irs.next()).getFormatName())) {
			throw new ImageFormatException("请上传JPG/JPEG或PNG类型图片");
		}
		reader.setInput(iis, Boolean.TRUE);
		return reader;
	}

	private boolean createDirectory(Path path) {
		File dir = path.toFile();
		if (dir.exists()) {
			return dir.isDirectory();
		} else {
			return dir.mkdirs();
		}
	}

	private AdOperateFileDO processFileName(MaterialFile materialFile) {
		MultipartFile mFile = materialFile.getFile();
		String originalFilename = mFile.getOriginalFilename();
		int idx = Objects.requireNonNull(originalFilename).lastIndexOf(".");
		String fileType = originalFilename.substring(idx + 1);
		return new AdOperateFileDO().setFileName(materialFile.getName()).setFileSize(mFile.getSize()).setFileType(fileType);
	}

	private Path processFileBytes(Path dirPath, MultipartFile mFile, AdOperateFileDO aof) throws IOException {
		aof.setFileMd5(DigestUtil.md5Hex(mFile.getBytes()));
		Path filePath = dirPath.resolve(aof.getFileMd5() + '.' + aof.getFileType());
		aof.setFilePath(filePath.toString());
		return filePath;
	}

	/**
	 * 编辑素材信息
	 *
	 * @param aom
	 */
	@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class)
	@Override
	public void updateMaterial(AdOperateMaterialVO aom) {
		Optional<AdOperateMaterialVO> aomOps = Optional.of(aom);
		// 素材ID
		Long materialId = aomOps.map(AdOperateMaterialVO::getMaterialId).orElseThrow(() -> new NullPointerException("素材ID不能为空"));
		AdOperateMaterialDO raw = adOperateMaterialMapper.selectById(aom.getMaterialId());
		if (null == raw) {
			return;
		}
		Integer currentUserId = Objects.requireNonNull(SecurityUtils.getUser()).getId();
		Date now = new Date();
		// 更新运营素材信息
		AdOperateMaterialDO record = new AdOperateMaterialDO().setMaterialId(materialId).setUpdateuser(currentUserId).setUpdatetime(now);
		aomOps.map(AdOperateMaterialVO::getPgid).filter(e -> !e.equals(raw.getPgid())).ifPresent(record::setPgid);
		aomOps.map(AdOperateMaterialVO::getCreateUserId).filter(e -> !e.equals(raw.getCreateUserId())).ifPresent(record::setCreateUserId);
		aomOps.map(AdOperateMaterialVO::getMakeUserId).filter(e -> !e.equals(raw.getMakeUserId())).ifPresent(record::setMakeUserId);
		aomOps.map(AdOperateMaterialVO::getMaterialType).filter(e -> !e.equals(raw.getMaterialType())).ifPresent(record::setMaterialType);
		adOperateMaterialMapper.updateById(record);
		// 更新运营素材关联的子游戏信息
		Set<Long> gameIds = aomOps.map(AdOperateMaterialVO::getGameidArr).filter(StringUtils::isNotEmpty).map(e -> Arrays.stream(e.split(",")).map(Long::valueOf).collect(Collectors.toSet())).orElse(Collections.emptySet());
		if (!gameIds.isEmpty()) {
			adOperateGameMapper.delete(Wrappers.<AdOperateGameDO>lambdaQuery().eq(AdOperateGameDO::getMaterialId, materialId));
			for (Long gameId : gameIds) {
				adOperateGameMapper.insert(new AdOperateGameDO().setMaterialId(materialId).setGameid(gameId));
			}
		}
		// 更新运营素材文件信息
		List<AdOperateFileDO> fileDOList = adOperateFileMapper.selectList(Wrappers.<AdOperateFileDO>lambdaQuery().eq(AdOperateFileDO::getMaterialId, aom.getMaterialId()));
		Set<String> fileNameSet = fileDOList.stream().map(AdOperateFileDO::getFileName).collect(Collectors.toSet());
		Map<Long, String> fileNameMap = fileDOList.stream().collect(Collectors.toMap(AdOperateFileDO::getId, AdOperateFileDO::getFileName, (k1, k2) -> k1));

		Collection<MaterialFile> materialFiles = aomOps.map(AdOperateMaterialVO::getMaterialFiles).orElse(Collections.emptyList());
		for (MaterialFile materialFile : materialFiles) {
			String fileName = assertFileName(materialFile);
			// 校验当前素材文件是否需要修改
			if (fileName.equals(fileNameMap.get(materialFile.getId()))) {
				continue;
			}
			if (0 == aom.getMaterialType()) {
				// 校验icon文件名称是否全局唯一
				if (adOperateFileMapper.selectCountByFileName(0, fileName) > 0) {
					throw new IllegalArgumentException("Icon类型图片名称已存在，请重命名");
				}
			} else if (1 == aom.getMaterialType()) {
				// 校验要修改的五图文件名是否已存在
				if (fileNameSet.contains(materialFile.getName())) {
					throw new IllegalArgumentException("五图图片名称不能重复");
				}
			}
			Optional<MaterialFile> fileNameOps = Optional.of(materialFile);
			Long id = fileNameOps.map(MaterialFile::getId).orElseThrow(() -> new NullPointerException("要修改的素材文件ID不能为空"));
			String name = fileNameOps.map(MaterialFile::getName).filter(StringUtils::isNotEmpty).orElseThrow(() -> new NullPointerException("要修改的素材文件名称不能为空"));
			adOperateFileMapper.updateById(new AdOperateFileDO().setId(id).setFileName(name).setUpdateuser(currentUserId).setUpdatetime(now));
		}
	}

	/**
	 * 推送Icon到头条或者广点通
	 *
	 * @param platformId
	 * @param materialId
	 * @param advertiserIds
	 * @return
	 * @throws IOException
	 * @throws ImageFormatException
	 */
	@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class)
	@Override
	public List<PushIconVO> pushIcon(Integer platformId, Long materialId, Collection<Long> advertiserIds) throws IOException, ImageFormatException {
		// 获取素材类型和主游戏ID
		AdOperateMaterialDO material = adOperateMaterialMapper.selectOne(Wrappers.<AdOperateMaterialDO>lambdaQuery().select(AdOperateMaterialDO::getMaterialType, AdOperateMaterialDO::getPgid).eq(AdOperateMaterialDO::getMaterialId, materialId));
		if (null == material || 0 != material.getMaterialType()) {
			throw new IllegalArgumentException("只能推送Icon类型素材");
		}
		// 获取文件信息
		final Path filePath;
		AdOperateFileDO oFile = adOperateFileMapper.selectOne(Wrappers.<AdOperateFileDO>lambdaQuery().select(AdOperateFileDO::getFileName, AdOperateFileDO::getFileType, AdOperateFileDO::getFilePath).eq(AdOperateFileDO::getMaterialId, materialId));
		if (null == oFile || Files.notExists(filePath = Paths.get(oFile.getFilePath()))) {
			throw new IllegalArgumentException("Icon文件不存在");
		}
		final String fileName = filePath.getFileName().toString();
		final String fileUrl = String.format("%s%s/%s", rootUrl, this.relativePath.replace("\\", "/"), fileName);

		if (Integer.parseInt(PlatformTypeEnum.TT.getValue()) == platformId) {
			return pushToTt(platformId, materialId, advertiserIds, oFile, filePath, fileName, fileUrl);
		} else if (Integer.parseInt(PlatformTypeEnum.GDT.getValue()) == platformId) {
			return pushToGdt(platformId, materialId, advertiserIds, oFile, filePath, fileName, fileUrl);
		} else {
			throw new IllegalArgumentException("未知的平台类型: " + platformId);
		}
	}

	/**
	 * 将运营icon作为卡片主图推送到头条
	 *
	 * @param materialId
	 * @param advertiserIds
	 * @param oFile
	 * @param filePath
	 * @param fileName
	 * @param fileUrl
	 * @return
	 * @throws IOException
	 * @throws ImageFormatException
	 */
	private List<PushIconVO> pushToTt(Integer platformId, Long materialId, Collection<Long> advertiserIds, AdOperateFileDO oFile, Path filePath, String fileName, String fileUrl) throws IOException, ImageFormatException {
		final String ttUri = "https://ad.oceanengine.com/open_api/2/file/image/ad/";

		// 调整图片尺寸和大小
		byte[] fileBytes = getFileContent(filePath, TT_ICON_PIXEL_SIZE, TT_ICON_MAX_SIZE);
		String md5Hex = DigestUtil.md5Hex(fileBytes);

		List<PushIconVO> resultList = new LinkedList<>();
		for (Long advertiserId : advertiserIds) {
			// 判断当前资源在当前广告账户下是否有推送过
			List<GdtBrandImage> images = gdtBrandImageMapper.selectList(Wrappers.<GdtBrandImage>lambdaQuery().eq(GdtBrandImage::getMaterialId, materialId).eq(GdtBrandImage::getAccountId, String.valueOf(advertiserId)).eq(GdtBrandImage::getPlatformId, platformId));
			if (images.size() > 0) {
				GdtBrandImage image = images.get(0);
				resultList.add(new PushIconVO().setSuccess(true).setMaterialId(image.getMaterialId()).setImageId(image.getImageId()).setAdvertiserId(image.getAccountId()).setImageUri(image.getPicUrl()));
				continue;
			}
			// 调用接口推送Icon卡片主图
			String accessToken = ttAccesstokenService.fetchAccesstoken(String.valueOf(advertiserId));

			String response = OEHttpUtils.postBuilder().uri(ttUri).accessToken(accessToken).httpEntity(MultipartEntityBuilder.create()
					.addTextBody("advertiser_id", String.valueOf(advertiserId))
					.addTextBody("image_signature", md5Hex)
					.addPart("image_file", new ByteArrayBody(fileBytes, fileName))
					.addTextBody("filename", oFile.getFileName())
					.build()).request();

//			HttpResponse response = HttpUtil.createRequest(Method.POST, ttUri).contentType(MediaType.MULTIPART_FORM_DATA_VALUE).header("Access-Token", accessToken)
//					.form("advertiser_id", advertiserId).form("image_signature", md5Hex).form("image_file", fileBytes, fileName).form("filename", oFile.getFileName())
//					.execute();

			JSONObject result = JSON.parseObject(response);

			if (0 != result.getInteger("code")) {
				resultList.add(new PushIconVO().setSuccess(false).setMessage(response));
				continue;
			}

			JSONObject data = result.getJSONObject("data");
			Date createTime = new Date();
			GdtBrandImage image = new GdtBrandImage()
					.setMaterialId(materialId).setImageId(data.getString("id")).setAccountId(String.valueOf(advertiserId)).setName(oFile.getFileName()).setWidth(data.getString("width")).setHeight(data.getString("height"))
					.setImageUrl(data.getString("url")).setCreatedTime(String.valueOf(createTime.getTime() / 1000L)).setPicFilename(fileName).setRealPath(oFile.getFilePath()).setPicUrl(fileUrl)
					.setMd5(md5Hex).setPicSize(String.valueOf(fileBytes.length)).setFormat(oFile.getFileType()).setPlatformId(platformId).setIsdelete(0).setCreatetime(createTime).setUpdatetime(createTime);
			gdtBrandImageMapper.insert(image);
			resultList.add(new PushIconVO().setSuccess(true).setMaterialId(image.getMaterialId()).setImageId(image.getImageId()).setAdvertiserId(image.getAccountId()).setImageUri(image.getPicUrl()));
		}
		return resultList;
	}

	/**
	 * 将icon作为品牌形象推送到广点通
	 *
	 * @param materialId
	 * @param advertiserIds
	 * @param oFile
	 * @param filePath
	 * @param fileName
	 * @param fileUrl
	 * @return
	 * @throws IOException
	 * @throws ImageFormatException
	 */
	private List<PushIconVO> pushToGdt(Integer platformId, Long materialId, Collection<Long> advertiserIds, AdOperateFileDO oFile, Path filePath, String fileName, String fileUrl) throws IOException, ImageFormatException {
		final String gdtUri = "https://api.e.qq.com/v1.1/brand/add?access_token=%s&timestamp=%s&nonce=%s";

		// 调整图片尺寸和大小
		byte[] fileBytes = getFileContent(filePath, GDT_ICON_PIXEL_SIZE, GDT_ICON_MAX_SIZE);
		String md5Hex = DigestUtil.md5Hex(fileBytes);

		List<PushIconVO> resultList = new LinkedList<>();
		for (Long advertiserId : advertiserIds) {
			// 判断当前资源在当前广告账户下是否有推送过
			List<GdtBrandImage> images = gdtBrandImageMapper.selectList(Wrappers.<GdtBrandImage>lambdaQuery().eq(GdtBrandImage::getMaterialId, materialId).eq(GdtBrandImage::getAccountId, String.valueOf(advertiserId)).eq(GdtBrandImage::getPlatformId, platformId));
			if (images.size() > 0) {
				GdtBrandImage image = images.get(0);
				resultList.add(new PushIconVO().setSuccess(true).setMaterialId(image.getMaterialId()).setImageId(image.getImageId()).setAdvertiserId(image.getAccountId()).setImageUri(image.getPicUrl()));
				continue;
			}
			// 调用接口推送Icon品牌
			Map<String, String> gdtCommonData = gdtAccesstokenService.fetchAccesstoken(String.valueOf(advertiserId));

			final String uri = String.format(gdtUri, gdtCommonData.get("access_token"), gdtCommonData.get("timestamp"), gdtCommonData.get("nonce"));
			String response = OEHttpUtils.postBuilder().uri(uri).httpEntity(MultipartEntityBuilder.create()
					.addTextBody("account_id", String.valueOf(advertiserId))
					.addTextBody("name", oFile.getFileName())
					.addPart("brand_image_file", new ByteArrayBody(fileBytes, fileName))
					.build()).request();

//			HttpResponse response = HttpUtil.createRequest(Method.POST, String.format(gdtUri, gdtCommonData.get("access_token"), gdtCommonData.get("timestamp"), gdtCommonData.get("nonce")))
//					.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
//					.form("access_token", gdtCommonData.get("access_token")).form("timestamp", Long.valueOf(gdtCommonData.get("timestamp"))).form("nonce", gdtCommonData.get("nonce"))
//					.form("account_id", advertiserId).form("name", oFile.getFileName()).form("brand_image_file", fileBytes, fileName).execute();

			JSONObject result = JSON.parseObject(response);

			if (0 != result.getInteger("code")) {
				resultList.add(new PushIconVO().setSuccess(false).setMessage(response));
				continue;
			}

			JSONObject data = result.getJSONObject("data");
			Date createTime = new Date(data.getLong("created_time") * 1000);
			GdtBrandImage image = new GdtBrandImage()
					.setMaterialId(materialId).setImageId(data.getString("image_id")).setAccountId(String.valueOf(advertiserId)).setName(oFile.getFileName()).setWidth(data.getString("width")).setHeight(data.getString("height"))
					.setImageUrl(data.getString("image_url")).setCreatedTime(data.getString("created_time")).setPicFilename(fileName).setRealPath(oFile.getFilePath()).setPicUrl(fileUrl)
					.setMd5(md5Hex).setPicSize(String.valueOf(fileBytes.length)).setFormat(oFile.getFileType()).setPlatformId(platformId).setIsdelete(0).setCreatetime(createTime).setUpdatetime(createTime);
			gdtBrandImageMapper.insert(image);
			resultList.add(new PushIconVO().setSuccess(true).setMaterialId(image.getMaterialId()).setImageId(image.getImageId()).setAdvertiserId(image.getAccountId()).setImageUri(image.getPicUrl()));
		}
		return resultList;
	}

	/**
	 * 编辑图片尺寸并压缩图片大小
	 *
	 * @param filePath
	 * @param requirePixel
	 * @param maxSize
	 * @return
	 * @throws IOException
	 * @throws ImageFormatException
	 */
	private byte[] getFileContent(Path filePath, int requirePixel, long maxSize) throws IOException, ImageFormatException {
		// 调整图片尺寸和大小
		byte[] fileBytes = Files.readAllBytes(filePath);
		try (ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes)) {
			ImageReader iReader = assertImageReader(bais, ALLOW_IMAGE_FORMAT_NAMES);
			if (iReader.getWidth(0) != iReader.getHeight(0)) {
				throw new ImageFormatException("Icon类型图片尺寸比例必须为1:1");
			}
			// 编辑Icon图片尺寸
			BufferedImage bImage = scaledImagePixel(requirePixel, iReader);
			// 压缩图片大小
			fileBytes = compressionImage((int) maxSize, bImage, fileBytes, iReader);
			if (fileBytes.length > maxSize) {
				throw new ImageFormatException(String.format("Icon类型图片压缩后大小为%dKB，已超过%dKB", fileBytes.length / 1024, maxSize / 1024));
			}
		}
		return fileBytes;
	}

	private static BufferedImage scaledImagePixel(int requirePixel, ImageReader iReader) throws IOException {
		if (requirePixel == iReader.getWidth(0)) {
			return null;
		}
		BufferedImage source = iReader.read(0);
		Image image = source.getScaledInstance(requirePixel, requirePixel, Image.SCALE_DEFAULT);
		BufferedImage target = new BufferedImage(requirePixel, requirePixel, source.getType());
		Graphics2D graphics = target.createGraphics();
		graphics.drawImage(image, 0, 0, null);
		graphics.dispose();
		return target;
	}

	private static byte[] compressionImage(int maxSize, BufferedImage bImage, byte[] rawFileBytes, ImageReader iReader) throws IOException {
		if (null == bImage) {
			// 图片尺寸符合要求，并没有编辑尺寸
			if (maxSize >= rawFileBytes.length) {
				return rawFileBytes;
			}
			// 大小不符合，需要压缩
			try (ByteArrayOutputStream baos = new ByteArrayOutputStream(rawFileBytes.length)) {
				bImage = iReader.read(0);
				ImageIO.write(bImage, iReader.getFormatName(), baos);
				compressionImage(maxSize, baos, bImage, iReader);
				return baos.toByteArray();
			}
		} else {
			// 图片尺寸不符合要求，编辑之后才符合的要求
			try (ByteArrayOutputStream baos = new ByteArrayOutputStream(rawFileBytes.length)) {
				ImageIO.write(bImage, iReader.getFormatName(), baos);
				if (maxSize < baos.size()) {
					// 大小不符合，需要压缩
					compressionImage(maxSize, baos, bImage, iReader);
				}
				return baos.toByteArray();
			}
		}
	}

	private static void compressionImage(int maxSize, ByteArrayOutputStream output, BufferedImage bImage, ImageReader iReader) throws IOException {
		ImageWriter iWriter = ImageIO.getImageWritersByFormatName(iReader.getFormatName()).next();
		ImageWriteParam writeParam = iWriter.getDefaultWriteParam();
		if (writeParam.canWriteCompressed()) {
			iWriter.setOutput(ImageIO.createImageOutputStream(output));
			float scaled = new BigDecimal(maxSize).divide(new BigDecimal(output.size()), 2, BigDecimal.ROUND_HALF_DOWN).floatValue();
			output.reset();
			writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
			writeParam.setCompressionQuality(scaled);
			writeParam.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
			iWriter.write(bImage);
			iWriter.dispose();
		} else if (iReader instanceof PNGImageReader) {
			try (ByteArrayInputStream bais = new ByteArrayInputStream(output.toByteArray())) {
				output.reset();
				PngCompressor.compress(bais, output);
			}
		}
	}

	/**
	 * 将运营素材打包
	 *
	 * @param materialId
	 * @return
	 * @throws IOException
	 */
	@Transactional(readOnly = true, rollbackFor = Exception.class)
	@Override
	public Path materialPackZip(Long materialId) throws IOException {
		if (null == materialId) {
			throw new NullPointerException("素材ID不能为空");
		}
		List<AdOperateFileDO> fileList = adOperateFileMapper.selectList(Wrappers.<AdOperateFileDO>lambdaQuery().select(AdOperateFileDO::getFileName, AdOperateFileDO::getFileType, AdOperateFileDO::getFilePath).eq(AdOperateFileDO::getMaterialId, materialId));
		if (fileList.isEmpty()) {
			throw new NullPointerException("不存在的运营素材");
		}
		// 校验文件是否存在
		if (fileList.stream().map(AdOperateFileDO::getFilePath).map(Paths::get).anyMatch(Files::notExists)) {
			throw new FileNotFoundException("未找到对应的文件");
		}
		// 打包文件
		Path zipPath = Paths.get(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString() + ".zip");
		try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipPath.toFile()))) {
			for (AdOperateFileDO operateFile : fileList) {
				String filePath = operateFile.getFilePath();
				String fileName = operateFile.getFileName() + "." + operateFile.getFileType();
				try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath))) {
					zos.putNextEntry(new ZipEntry(fileName));
					byte[] buffer = new byte[1024];
					for (int len; (len = bis.read(buffer)) > 0; ) {
						zos.write(buffer, 0, len);
					}
				} finally {
					zos.closeEntry();
				}
			}
		}
		return zipPath;
	}

	/**
	 * 删除运营素材
	 *
	 * @param materialIds
	 */
	@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class)
	@Override
	public void delete(Collection<Long> materialIds) {
		Date now = new Date();
		Integer currentUserId = Objects.requireNonNull(SecurityUtils.getUser()).getId();
		gdtBrandImageMapper.update(new GdtBrandImage().setIsdelete(1).setUpdateuser(String.valueOf(currentUserId)).setUpdatetime(now), Wrappers.<GdtBrandImage>lambdaUpdate().in(GdtBrandImage::getMaterialId, materialIds));
		this.update(Wrappers.<AdOperateMaterialDO>lambdaUpdate().set(AdOperateMaterialDO::getIsdelete, 1).set(AdOperateMaterialDO::getUpdateuser, currentUserId).set(AdOperateMaterialDO::getUpdatetime, now).in(AdOperateMaterialDO::getMaterialId, materialIds));
	}

}
